package jmine.tec.report.export;

import java.text.DateFormat;
import java.text.NumberFormat;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Map.Entry;

import jmine.tec.report.DataRow;
import jmine.tec.report.DataTable;
import jmine.tec.report.cell.DataCell;
import jmine.tec.report.cell.impl.AbstractDoubleValueDataCell;
import jmine.tec.report.cell.impl.CompositeDataCell;
import jmine.tec.report.cell.impl.CurrencyDataCell;
import jmine.tec.report.cell.impl.DateDataCell;
import jmine.tec.report.cell.impl.PercentageDataCell;

import org.apache.poi.hssf.usermodel.HSSFCell;
import org.apache.poi.hssf.usermodel.HSSFCellStyle;
import org.apache.poi.hssf.usermodel.HSSFDataFormat;
import org.apache.poi.hssf.usermodel.HSSFRichTextString;
import org.apache.poi.hssf.usermodel.HSSFRow;
import org.apache.poi.hssf.usermodel.HSSFSheet;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;

/**
 * @author takeshi
 */
public class HSSFExporter {

    private static final int NUMBER_OF_STYLES = 4;

    private String dateStyle = "dd/MM/yyyy";

    private String numberStyle = "0.00";

    private String currencyStyle = "0.00";

    private DateFormat dateFormat = DateFormat.getDateInstance();

    private NumberFormat numberFormat = NumberFormat.getInstance();

    private NumberFormat currencyFormat = NumberFormat.getCurrencyInstance();

    private Locale locale;

    private static final int DATE_FORMAT_CELL_STYLE = 0;

    private static final int NUMBER_FORMAT_CELL_STYLE = 1;

    private static final int PERCENTAGE_FORMAT_CELL_STYLE = 2;

    private static final int CURRENCY_FORMAT_CELL_STYLE = 3;

    /**
     * @return the locale
     */
    public Locale getLocale() {
        return this.locale;
    }

    /**
     * @param locale the locale to set
     */
    public void setLocale(final Locale locale) {
        this.locale = locale;
    }

    /**
     * @return the currencyFormat
     */
    public NumberFormat getCurrencyFormat() {
        return this.currencyFormat;
    }

    /**
     * @param currencyFormat the currencyFormat to set
     */
    public void setCurrencyFormat(final NumberFormat currencyFormat) {
        this.currencyFormat = currencyFormat;
    }

    /**
     * @return the currencyStyle
     */
    public String getCurrencyStyle() {
        return this.currencyStyle;
    }

    /**
     * @param currencyStyle the currencyStyle to set
     */
    public void setCurrencyStyle(final String currencyStyle) {
        this.currencyStyle = currencyStyle;
    }

    /**
     * @return the dateFormat
     */
    public DateFormat getDateFormat() {
        return this.dateFormat;
    }

    /**
     * @param dateFormat the dateFormat to set
     */
    public void setDateFormat(final DateFormat dateFormat) {
        this.dateFormat = dateFormat;
    }

    /**
     * @return the dateStyle
     */
    public String getDateStyle() {
        return this.dateStyle;
    }

    /**
     * @param dateStyle the dateStyle to set
     */
    public void setDateStyle(final String dateStyle) {
        this.dateStyle = dateStyle;
    }

    /**
     * @return the numberFormat
     */
    public NumberFormat getNumberFormat() {
        return this.numberFormat;
    }

    /**
     * @param numberFormat the numberFormat to set
     */
    public void setNumberFormat(final NumberFormat numberFormat) {
        this.numberFormat = numberFormat;
    }

    /**
     * @return the numberStyle
     */
    public String getNumberStyle() {
        return this.numberStyle;
    }

    /**
     * @param numberStyle the numberStyle to set
     */
    public void setNumberStyle(final String numberStyle) {
        this.numberStyle = numberStyle;
    }

    /**
     * Exports a {@link DataTable} as a MS-Excel format.
     * 
     * @param table the {@link DataTable} to export
     * @param worksheetName the worksheet name
     * @param columnHeaders the column headers
     * @return {@link HSSFWorkbook}
     */
    public HSSFWorkbook export(final DataTable table, final String worksheetName, final String[] columnHeaders) {
        HSSFWorkbook wb = new HSSFWorkbook();
        this.export(table, wb, worksheetName, columnHeaders);
        return wb;
    }

    /**
     * Exporta o {@link DataTable} no {@link HSSFWorkbook}, usando um worksheet com o nome passado
     * 
     * @param table {@link DataTable}
     * @param workbook {@link HSSFWorkbook}
     * @param worksheetName {@link StringIndexOutOfBoundsException}
     * @param columnHeaders the column headers
     */
    public void export(final DataTable table, final HSSFWorkbook workbook, final String worksheetName,
            final String[] columnHeaders) {
        HSSFSheet sheet =
                ((workbook.getSheet(worksheetName) != null) ? workbook.getSheet(worksheetName) : workbook
                .createSheet(worksheetName));
        HSSFCellStyle[] styles = this.createCellStyles(workbook);

        final List<DataRow> rows = table.getRows();
        this.createHeader(columnHeaders, sheet, (short) (sheet.getLastRowNum() + 2));
        this.createRows(workbook, sheet, rows, (short) (sheet.getLastRowNum() + 1), styles);
    }

    /**
     * @param table {@link DataTable}
     * @param atributos {@link Map}
     * @param workbook {@link HSSFWorkbook}
     * @param title o título da planilha
     * @param header os cabeçalhos das colunas
     */
    public void exportWithAtrib(final DataTable table, final Map<String, Object> atributos,
            final HSSFWorkbook workbook, final String title, final String[] header) {
        short rowIndex = 0;
        HSSFCellStyle[] styles = this.createCellStyles(workbook);
        HSSFSheet sheet = workbook.createSheet(title);
        HSSFRow hssfRow;
        Map.Entry<String, Object> entryPair;

        for (Iterator<Entry<String, Object>> i = atributos.entrySet().iterator(); i.hasNext();) {
            hssfRow = sheet.createRow(rowIndex);
            entryPair = i.next();
            this.createAttributeCell(hssfRow, entryPair.getKey(), entryPair.getValue(), styles);
            rowIndex++;
        }
        this.export(table, workbook, title, header);
    }

    /**
     * @param workbook {@link HSSFWorkbook}
     * @return array of {@link HSSFCellStyle}
     */
    private HSSFCellStyle[] createCellStyles(final HSSFWorkbook workbook) {
        final HSSFCellStyle[] cellStyles = new HSSFCellStyle[NUMBER_OF_STYLES];
        final HSSFDataFormat dataFormat = workbook.createDataFormat();
        for (int i = 0; i < cellStyles.length; i++) {
            cellStyles[i] = workbook.createCellStyle();
        }
        cellStyles[DATE_FORMAT_CELL_STYLE].setDataFormat(dataFormat.getFormat(this.getDateStyle()));
        cellStyles[NUMBER_FORMAT_CELL_STYLE].setDataFormat(dataFormat.getFormat(this.getNumberStyle()));
        cellStyles[PERCENTAGE_FORMAT_CELL_STYLE].setDataFormat(dataFormat.getFormat(this.getNumberStyle() + "%"));
        cellStyles[CURRENCY_FORMAT_CELL_STYLE].setDataFormat(dataFormat.getFormat(this.getCurrencyStyle()));
        return cellStyles;
    }

    /**
     * Creates a header for this table.
     * 
     * @param columnHeaders the {@link String} with column header
     * @param sheet {@link HSSFSheet} the sheet
     * @param index short
     */
    private void createHeader(final String[] columnHeaders, final HSSFSheet sheet, final short index) {
        int i = 0;
        final HSSFRow row = sheet.createRow(index);
        for (String header : columnHeaders) {
            row.createCell(i++).setCellValue(new HSSFRichTextString(header));
        }
    }

    /**
     * @param wb the {@link HSSFWorkbook}
     * @param sheet the {@link HSSFSheet}
     * @param rows the {@link List} of {@link DataRow}
     * @param rowIndex the starting row index
     * @param styles array of {@link HSSFCellStyle}
     * @return short the last valid index
     */
    private short createRows(final HSSFWorkbook wb, final HSSFSheet sheet, final List<? extends DataRow> rows,
            final short rowIndex, final HSSFCellStyle[] styles) {
        short i = rowIndex;
        for (DataRow row : rows) {
            HSSFRow hssfRow = sheet.createRow(i++);
            short col = 0;

            this.createCell(wb, hssfRow, col++, row.getNameDataCell(), styles);

            for (DataCell cell : row.dataCellIterable()) {
                this.createCell(wb, hssfRow, col++, cell, styles);
            }
            if (!row.getChildRows().isEmpty()) {
                i = this.createRows(wb, sheet, row.getChildRows(), i, styles);
                sheet.createRow(i++).createCell(0).setCellValue(new HSSFRichTextString(" "));
            }

        }
        return i;
    }

    /**
     * @param wb {@link HSSFWorkbook}
     * @param hssfRow {@link HSSFRow}
     * @param col cel index
     * @param cell the {@link DataCell}
     * @param styles array of {@link HSSFCellStyle}
     */
    private void createCell(final HSSFWorkbook wb, final HSSFRow hssfRow, final int col, final DataCell cell,
            final HSSFCellStyle[] styles) {
        if (cell instanceof DateDataCell) {
            this.createDateCell(hssfRow, col, cell, styles[DATE_FORMAT_CELL_STYLE]);
        } else if (cell instanceof AbstractDoubleValueDataCell) {
            this.createNumberCell(hssfRow, col, cell, styles);
        } else if (cell.toString().contains("nave.")) {
            /*
             * Verifica se a célula contém alguma mensagem; se tiver, uma célula vazia é criada.
             */
            hssfRow.createCell(col, HSSFCell.CELL_TYPE_BLANK).setCellValue(new HSSFRichTextString(""));
        } else if (cell instanceof CompositeDataCell) {
            if (((CompositeDataCell) cell).toString(this.getDateFormat(), this.getNumberFormat(),
                    this.getCurrencyFormat()).equalsIgnoreCase("[nave.celulas.vazia, nave.celulas.vazia]")) {
                hssfRow.createCell(col).setCellValue(new HSSFRichTextString(""));
            } else {
                hssfRow.createCell(col).setCellValue(
                        new HSSFRichTextString(cell.toString(this.dateFormat, this.numberFormat, this.currencyFormat)));
            }
        } else {
            hssfRow.createCell(col).setCellValue(
                    new HSSFRichTextString(cell.toString(this.getDateFormat(), this.getNumberFormat(), this
                    .getCurrencyFormat())));
        }
    }

    /**
     * @param hssfRow the {@link HSSFRow}
     * @param s the cell index
     * @param cell the {@link DataCell}
     * @param styles array of {@link HSSFCellStyle}
     */
    private void createNumberCell(final HSSFRow hssfRow, final int s, final DataCell cell,
            final HSSFCellStyle[] styles) {
        final HSSFCell hssfCell = hssfRow.createCell(s);
        final Double value = cell.toDouble();
        hssfCell.setCellValue(value.isNaN() ? 0.0 : value.doubleValue());
        HSSFCellStyle cellStyle;
        if (cell instanceof PercentageDataCell) {
            cellStyle = styles[PERCENTAGE_FORMAT_CELL_STYLE];
        } else if (cell instanceof CurrencyDataCell) {
            cellStyle = styles[CURRENCY_FORMAT_CELL_STYLE];
        } else {
            cellStyle = styles[NUMBER_FORMAT_CELL_STYLE];
        }
        hssfCell.setCellStyle(cellStyle);
    }

    /**
     * @param hssfRow the {@link HSSFRow}
     * @param s the cell index
     * @param cell the {@link DataCell}
     * @param cellStyle the {@link HSSFCellStyle}
     */
    private void createDateCell(final HSSFRow hssfRow, final int s, final DataCell cell, final HSSFCellStyle cellStyle) {
        final HSSFCell createCell = hssfRow.createCell(s);
        createCell.setCellValue(cell.toDate());
        createCell.setCellStyle(cellStyle);
    }

    /**
     * Método que insere na planilha os atributos dos relatórios de carteira.
     * 
     * @param hssfRow uma linha da planilha
     * @param key o nome do atributo
     * @param value o valor do atributo
     * @param styles HSSFCellStyle[]
     */
    private void createAttributeCell(final HSSFRow hssfRow, final String key, final Object value,
            final HSSFCellStyle[] styles) {
        HSSFCell cell;
        if (value != null) {
            hssfRow.createCell(0).setCellValue(new HSSFRichTextString(key));
            cell = hssfRow.createCell(1);
            if (value instanceof Date) {
                cell.setCellValue((Date) value);
                cell.setCellStyle(styles[DATE_FORMAT_CELL_STYLE]);
            } else if (value instanceof Number) {
                if (value instanceof Integer) {
                    int intVal = ((Integer) value).intValue();
                    cell.setCellValue(intVal);
                    cell.setCellStyle(styles[NUMBER_FORMAT_CELL_STYLE]);
                } else if (value instanceof Double) {
                    double dblVal = ((Double) value).isNaN() ? 0.0 : ((Double) value).doubleValue();
                    cell.setCellValue(dblVal);
                    cell.setCellStyle(styles[NUMBER_FORMAT_CELL_STYLE]);
                }
            } else {
                cell.setCellValue(new HSSFRichTextString(value.toString()));
            }
        }
    }
}
